<?php
//
//	file: sys/sys.class.php
//	author: Pierre ROGE
//	begin: 02/06/2007
//	version: 0.0.3 - 18/08/2008
//	license: http://opensource.org/licenses/gpl-license.php GNU General Public License (GPL)
//

// php minimal version:
//	- regexp /u: LINUX: php 4.1.0, WINDOWS: php 4.2.3 (with utf-8)
//	- htmlspecialchars(,, 'UTF-8'): php 4.1.0 (with utf-8)
//	- pg_fetch_row(): php 4.1.0 : (sys_db_pgsql, pg_fetch_row() without row idx)
//	- version_compare(): php 4.0.7

// SYS constants define the varname of our unique object which handles system status
if ( !defined('SYS') )
{
	die('Not allowed');
}
if ( isset($GLOBALS[SYS]) )
{
	die('Conflict with varname $' . SYS);
}
define('SYS_PHP_VERSION_5', version_compare(PHP_VERSION, '5.0.0', '>='));

// root class: all classes are inherited from this one
class sys_stdclass
{
	var $api_name;

	function sys_stdclass($api_name)
	{
		$this->__construct($api_name);
	}

	function destroy()
	{
		if ( !SYS_PHP_VERSION_5 )
		{
			$this->__destruct();
		}
	}

	function __construct($api_name)
	{
		$this->api_name = $api_name;
	}

	function __destruct()
	{
		if ( isset($this->api_name) )
		{
			unset($this->api_name);
		}
	}

	function set()
	{
		return true;
	}
}

// class for SYS object which handles the system objects and properties that are unique (eg rstats, lang, ...).
class sys extends sys_stdclass
{
	var $root;
	var $requester;
	var $ext;
	var $path;
	var $ini;

	var $rstats;
	var $lang;
	var $utf8;
	var $error;
	var $io;
	var $tpl;
	var $objects;
	var $shutdown_registered;
	var $error_reporting;
	var $magic_quotes_runtime;

	function __construct($api_name)
	{
		// $api_name is the varname ($GLOBALS[$api_name]) of this sys instance creator, so the main api
		parent::__construct($api_name);

		// paths
		$this->root = false; // relative path from the page displayed to the site root url (./ for the main index)
		$this->requester = false; // script path and name without the extension relative to the site root url ($this->root)
		$this->ext = false; // this file extension
		$this->path = false; // relative path from $this->root to the directory where stands this file (so $this->root . 'sys/')
		$this->ini = false; // ini values

		// system status
		$this->shutdown_registered = false;
		$this->error_reporting = false;
		$this->magic_quotes_runtime = false;

		// main objects
		$this->lang = false;
		$this->utf8 = false;
		$this->error = false;
		$this->io = false;
		$this->tpl = false;

		// registered objects
		$this->objects = false;
	}

	function __destruct()
	{
		if ( !isset($this->api_name) )
		{
			return false;
		}

		// close all the registered global objects, starting with the last registered
		while ( !empty($this->objects) )
		{
			$name = array_pop($this->objects);
			if ( isset($GLOBALS[$name]) )
			{
				sys::kill($GLOBALS[$name]);
				unset($GLOBALS[$name]);
			}
		}
		$this->objects = false;

		// destroy tpl
		if ( isset($this->tpl) )
		{
			sys::kill($this->tpl);
			unset($this->tpl);
		}

		// destroy io
		if ( isset($this->io) )
		{
			sys::kill($this->io);
			unset($this->io);
		}

		// destroy error
		if ( isset($this->error) )
		{
			sys::kill($this->error);
			unset($this->error);
		}

		// destroy lang
		if ( isset($this->utf8) )
		{
			sys::kill($this->utf8);
			unset($this->utf8);
		}

		// destroy lang
		if ( isset($this->lang) )
		{
			sys::kill($this->lang);
			unset($this->lang);
		}

		if ( isset($this->rstats) )
		{
			sys::kill($this->rstats);
			unset($this->rstats);
		}

		// restore magic_quotes_runtime status
		if ( $this->magic_quotes_runtime !== false )
		{
			if ( $this->magic_quotes_runtime )
			{
				set_magic_quotes_runtime(1);
			}
		}
		unset($this->magic_quotes_runtime);

		// restore error_reporting level
		if ( $this->error_reporting !== false )
		{
			error_reporting($this->error_reporting);
		}
		unset($this->error_reporting);
		unset($this->shutdown_registered);

		unset($this->ini);
		unset($this->path);
		unset($this->ext);
		unset($this->requester);
		unset($this->root);
		parent::__destruct();
	}

	function set($root, $requester, $path=false, $skip_server_status=false)
	{
		parent::set();

		// requester
		$this->root = $root;
		$this->requester = $requester;
		$this->ext = strrchr(__FILE__, '.');
		$this->path = $path ? $path : $this->root . 'sys/';

		// set the server status
		if ( !$skip_server_status )
		{
			// previous error reporting level
			$this->error_reporting = error_reporting(E_ALL ^ E_NOTICE);

			// init randomizer
			if ( version_compare(PHP_VERSION, '4.2.0', '<') )
			{
				list($usec, $sec) = explode(' ', $time);
				$seed = (float) $sec + ((float) $usec * 100000);
				mt_srand($seed);
			}

			// get magic_quotes_runtime status
			if ( ($this->magic_quotes_runtime = get_magic_quotes_runtime()) )
			{
				set_magic_quotes_runtime(0);
			}
		}

		// set the system ini values
		$this->ini_set();
		return true;
	}

	// create the system dependant objects
	function set_context($start_time=false)
	{
		// shutdown function
		if ( ($this->shutdown_registered = !isset($this->shutdown_registered) || ($this->shutdown_registered === false)) && !SYS_PHP_VERSION_5 )
		{
			register_shutdown_function($this->ini_get('shutdown_function', 'callback'));
		}

		// run stats reports handler
		if ( (!isset($this->rstats) || ($this->rstats === false)) && ($class = $this->ini_get('rstats', 'class')) )
		{
			$this->rstats = new $class($this->api_name);
			$this->rstats->set();
		}
		if ( isset($this->rstats) && is_object($this->rstats) )
		{
			$this->rstats->register('script', array('title' => $this->api_name, 'tick' => $start_time));
		}

		// lang handler
		if ( (!isset($this->lang) || ($this->lang === false)) && ($class = $this->ini_get('lang', 'class')) )
		{
			$this->lang = new $class($this->api_name);
			$this->lang->set();
		}

		// we can now register the scripts to the lang object
		if ( isset($this->lang) && is_object($this->lang) )
		{
			$this->lang->register('sys');
			$this->lang->register('sys.datetime');
		}

		// error handler
		if ( (!isset($this->error) || ($this->error === false)) && ($class = $this->ini_get('error', 'class')) )
		{
			$this->error = new $class($this->api_name);
			$this->error->set();
		}

		// utf-8 handler
		if ( (!isset($this->utf8) || ($this->utf8 === false)) && ($class = $this->ini_get('utf8', 'class')) && ($class = call_user_func(array($class, 'get_layer'))) )
		{
			$this->utf8 = new $class($this->api_name);
			$this->utf8->set();
		}

		// io layer
		if ( (!isset($this->io) || ($this->io === false)) && ($class = $this->ini_get('io', 'class')) )
		{
			$this->io = new $class($this->api_name);
			$this->io->set();
		}

		// template layer
		if ( (!isset($this->tpl) || ($this->tpl === false)) && ($class = $this->ini_get('tpl', 'class')) )
		{
			$this->tpl = new $class($this->api_name);
			$this->tpl->set();
		}

		// register our system tpl path
		if ( isset($this->tpl) && is_object($this->tpl) )
		{
			// we expect the sys tpl cache path to be $this->root . 'cache/'
			$this->tpl->register_tpl($this->root . 'styles/sys.tpls/', '.tpl', $this->root . 'cache/', 'sys');
		}
	}

	// register global objects we want to kill with the system one
	function register($name)
	{
		if ( isset($GLOBALS[$name]) )
		{
			if ( !$this->objects )
			{
				$this->objects = array();
			}
			$id = count($this->objects);
			$this->objects[$id] = $name;
			return $id;
		}
		return false;
	}

	//
	// ini
	//

	// our system ini core values
	function ini_default()
	{
		$this->ini = array(
			// shutdown function when php < 5.0.0: sys::destroy()
			'shutdown_function' => array('callback' => array('sys', 'destroy')),

			// main handlers (SYS object)
			'rstats' => array('class' => 'sys_rstats', 'file' => 'SYS/rstats.class'),
			'lang' => array('class' => 'sys_lang', 'file' => 'SYS/lang.class'),
			'lang.dir' => array('dir' => 'ROOT/languages'),
			'error' => array('class' => 'sys_error', 'file' => 'SYS/error.mixed'),
			'io' => array('class' => 'sys_io', 'file' => 'SYS/io.class'),
			'tpl' => array('class' => 'sys_tpl', 'file' => 'SYS/tpl.class'),
			'tpl.compiler' => array('class' => 'sys_tpl_compiler', 'file' => 'SYS/tpl.compiler.class'),
			'tpl.img' => array('class' => 'sys_tpl_img', 'file' => 'SYS/tpl.img.class'),

			// utf-8
			'utf8' => array('class' => 'sys_utf8', 'file' => 'SYS/utf8/utf8.class'),
		);
	}

	// set the ini values
	// entry:
	//		o none: simply set the $this->ini values if not already done
	//		o key::string, value::mixed: set value for key
	//		o key::array(string => mixed, ...): merge the array to $this->ini
	function ini_set($key=false, $value=false)
	{
		if ( !$this->ini )
		{
			$this->ini_default();
		}
		if ( !empty($key) )
		{
			if ( is_array($key) )
			{
				$this->ini = array_merge($this->ini, $key);
			}
			else
			{
				$this->ini[$key] = $value;
			}
		}
	}

	function ini_get($key, $attr=false)
	{
		if ( !$this->ini )
		{
			$this->ini_set();
		}
		if ( !$key || !$this->ini || !isset($this->ini[$key]) )
		{
			return false;
		}
		switch ( $attr )
		{
			// dir: return the directory name: doesn't check the existence
			case 'dir':
				return isset($this->ini[$key]['dir']) ? $this->get_layer_file($this->ini[$key]['dir'], true) : false;

			// uri: return the uri path, without the extension
			case 'uri':
				return isset($this->ini[$key]['uri']) ? $this->get_layer_file($this->ini[$key]['uri'], true) : false;

			// file: return the filename (plus extension): check the existence
			case 'file':
				if ( !isset($this->ini[$key]['file']) )
				{
					return false;
				}
				$found = false;
				if ( ($name = $this->get_layer_file($this->ini[$key]['file'])) )
				{
					if ( is_array($name) )
					{
						foreach ( $name as $fname )
						{
							$found |= ($fname && ($filename = sys::realpath($fname)) && file_exists($filename));
						}
					}
					else
					{
						$found = ($filename = sys::realpath($name)) && file_exists($filename);
					}
				}
				return $found ? $name : false;

			// layer: load the file
			case 'layer':
				if ( !isset($this->ini[$key]['file']) || !($name = $this->get_layer_file($this->ini[$key]['file'])) )
				{
					return false;
				}
				if ( (isset($this->ini[$key]['class']) && class_exists($this->ini[$key]['class'])) || (isset($this->ini[$key]['function']) && function_exists($this->ini[$key]['function'])) )
				{
					return $name;
				}

				$found = false;
				if ( is_array($name) )
				{
					foreach ( $name as $fname )
					{
						if ( ($found |= ($fname && ($filename = sys::realpath($fname)) && file_exists($filename))) )
						{
							include_once($fname);
						}
					}
				}
				else if ( ($found = ($filename = sys::realpath($name)) && file_exists($filename)) )
				{
					include_once($name);
				}
				return $found ? $name : false;

			// class: return the class name, load the layer if necessary
			case 'class':
				$found = class_exists($this->ini[$key][$attr]) || ($this->ini_get($key, 'layer') && class_exists($this->ini[$key][$attr]));
				return $found ? $this->ini[$key][$attr] : false;

			default:
				return $this->ini[$key];
		}
	}

	// turn a layer name to a layer file, with or without the extension
	function get_layer_file($name, $no_ext=false)
	{
		if ( is_array($name) )
		{
			return array_map(array($this, 'get_layer_file'), $name);
		}
		return $name ? str_replace(array('ROOT/', 'SYS/'), array($this->root, $this->path), $name) . ($no_ext ? '' : $this->ext) : false;
	}

	//
	// basic functions
	//

	// static: check if we have an object and run the destroy() method if exists and is required (eg PHP < 5.0.0)
	function kill(&$obj)
	{
		if ( !SYS_PHP_VERSION_5 && is_object($obj) && method_exists($obj, 'destroy') )
		{
			$obj->destroy();
		}
	}

	// static: htmlspecialchars
	function htmlspecialchars($str)
	{
		return isset($GLOBALS[SYS]) && isset($GLOBALS[SYS]->utf8) && is_object($GLOBALS[SYS]->utf8) ? htmlspecialchars($str, ENT_COMPAT, 'UTF-8') : htmlspecialchars($str, ENT_COMPAT);
	}

	// static: reverse html_specialchars
	// - entry: string
	// - return: htmlspecialchars_decode($str)
	function htmlspecialchars_decode($str)
	{
		static $done, $exists, $html_translation_table_fliped;
		if ( !$done && ($done = true) )
		{
			$html_translation_table_fliped = false;
			if ( !($exists = function_exists('htmlspecialchars_decode')) )
			{
				if ( function_exists('get_html_translation_table') )
				{
					$html_translation_table_fliped = array_flip(get_html_translation_table(HTML_ENTITIES));
				}
				if ( !$html_translation_table_fliped )
				{
					$html_translation_table_fliped = array('&amp;' => '&', '&#039;' => '\'', '&quot;' => '"', '&lt;' => '<', '&gt;' => '>');
				}
			}
		}
		return $exists ? htmlspecialchars_decode($str) : strtr($str, $html_translation_table_fliped);
	}

	// static: strtolower utf8 aware
	function strtolower($str)
	{
		return isset($GLOBALS[SYS]) && isset($GLOBALS[SYS]->utf8) && is_object($GLOBALS[SYS]->utf8) ? $GLOBALS[SYS]->utf8->strtolower($str) : strtolower($str);
	}

	// static: trim or normalize
	function normalize($str)
	{
		return isset($GLOBALS[SYS]) && isset($GLOBALS[SYS]->utf8) && is_object($GLOBALS[SYS]->utf8) ? $GLOBALS[SYS]->utf8->normalize(trim($str)) : trim($str);
	}

	// static: format microtime() result
	// - entry: none
	// - return: microtime() in seconds
	function microtime()
	{
		list($usec, $sec) = explode(' ', microtime());
		return (float) $usec + (float) $sec;
	}

	// static: check if realpath() is available and return the file real path if possible
	function realpath($path)
	{
		static $done, $realpath_exists;
		if ( !$done && ($done = true) )
		{
			$checkpath = function_exists('realpath') ? @realpath('.') : false;
			$realpath_exists = $checkpath && ($checkpath != '.');
		}
		return $realpath_exists ? @realpath($path) : $path;
	}

	// static: check if the directory exists
	function dir_exists($dir)
	{
		return ($filename = sys::realpath($dir . '.')) && file_exists($filename);
	}

	// static: used to write cache files ie
	function write($fullpath, &$content, $skip_error=false)
	{
		if ( ($fp = @fopen($fullpath, 'wb')) )
		{
			@flock($fp, LOCK_EX);
			@fwrite($fp, $content);
			@flock($fp, LOCK_UN);
			@fclose($fp);
			@umask(0000);
			@chmod($fullpath, 0644);
			return true;
		}
		trigger_error(sys_error::sprintf('err_sys_write', basename($fullpath)), $skip_error ? E_USER_NOTICE : E_USER_ERROR);
		return false;
	}

	// static: wrapper for file_get_contents()
	function file_get_contents($realpath)
	{
		$content = false;
		if ( function_exists('file_get_contents') )
		{
			$content = file_get_contents($realpath);
		}
		else if ( ($fp = @fopen($realpath, 'r')) )
		{
			$content = trim(@fread($fp, $fsize));
			@fclose($fp);
		}
		return $content;
	}

	// static: regular expressions
	function preg_expr($type)
	{
		switch ( $type )
		{
			case 'email':
				return '^[a-z0-9&\'\.\-_\+]+@[a-z0-9\-]+\.([a-z0-9\-]+\.)*?[a-z]+$';
		}
		return false;
	}
}

// this is our main object, but without the page handling
class sys_api extends sys_stdclass
{
	var $local_db;
	var $local_session;
	var $dft_values;
	var $db;
	var $session;

	function __construct($api_name)
	{
		// $api_name is the varname ($GLOBALS[$api_name]) of this instance: it will be propagated to all dependant objects
		parent::__construct($api_name);
		$this->local_db = false;
		$this->local_session = false;
		$this->dft_values = false;
		$this->db = false;
		$this->session = false;
	}

	function __destruct()
	{
		if ( !isset($this->api_name) )
		{
			return false;
		}

		if ( isset($this->session) )
		{
			if ( $this->local_session )
			{
				sys::kill($this->session);
			}
			unset($this->session);
		}
		if ( isset($this->db) )
		{
			if ( $this->local_db )
			{
				sys::kill($this->db);
			}
			unset($this->db);
		}
		unset($this->dft_values);
		unset($this->local_session);
		unset($this->local_db);
		parent::__destruct();
	}

	function set($root, $requester)
	{
		$start = sys::microtime();
		parent::set();
		$this->dft_values = false;

		// the sys object is not yet created: do it now
		if ( !isset($GLOBALS[SYS]) )
		{
			// instanciate the sys object
			$GLOBALS[SYS] = new sys($this->api_name);
			$GLOBALS[SYS]->set($root, $requester);

			// set the ini values for this api
			$this->ini_set();

			// set the sys object context
			$GLOBALS[SYS]->set_context($start);
		}
		// the sys object exists: we just need to set the ini values for this api
		else
		{
			$this->ini_set();
		}

		// register this object
		$sys = &$GLOBALS[SYS];
		$sys->register($this->api_name);

		// open the database
		$this->start_db();

		// open session
		if ( isset($this->db) && is_object($this->db) && ($this->local_session = !isset($this->session) || !is_object($this->session)) )
		{
			if ( ($class = $sys->ini_get('session', 'class')) )
			{
				$this->session = new $class($this->api_name);
				$this->session->set();
			}
		}
		if ( isset($this->session) && is_object($this->session) )
		{
			$this->session->open();
		}
	}

	// set default values
	// input:
	//	- array($field => $value, ...) or
	//	- field, value
	function set_dft_values()
	{
		$args = func_get_args();
		$this->dft_values = array_merge($this->dft_values ? $this->dft_values : array(), isset($args[1]) ? array($args[0] => $args[1]) : $args[0]);
	}

	// retrieve/a default value
	// input:
	//	o varname
	//	o default if missing
	// output: value
	function get_dft_values()
	{
		$args = func_get_args();
		if ( !isset($this->dft_values[ $args[0] ]) && isset($args[1]) )
		{
			$this->set_dft_values($args[0], $args[1]);
		}
		return isset($this->dft_values[ $args[0] ]) ? $this->dft_values[ $args[0] ] : false;
	}

	function remove_dft_values($parms)
	{
		if ( $parms && $this->dft_values )
		{
			foreach ( $parms as $parm => $value )
			{
				if ( isset($this->dft_values[$parm]) && ($this->dft_values[$parm] == $value) )
				{
					unset($parms[$parm]);
				}
			}
		}
		return $parms;
	}

	function ini_set()
	{
		$sys = &$GLOBALS[SYS];
		$sys->ini_set(array(
			// db: insert_group: max number of rows with insert
			'db' => array('class' => 'sys_db', 'file' => 'SYS/db/db.class', 'max_insert' => 50),
			'dbconfig' => array('file' => 'ROOT/config'),

			// heap management
			'tree' => array('class' => 'sys_tree', 'file' => 'SYS/tree.class'),
			'tree.list' => array('class' => 'sys_tree_list', 'file' => 'SYS/tree.list.class'),
			'tree.form' => array('class' => 'sys_tree_form', 'file' => 'SYS/tree.form.class'),
			'tree.fields' => array('class' => 'sys_field_tree_parent', 'file' => 'SYS/tree.fields.class'),

			// xml parser
			'xml' => array('class' => 'sys_xml_parser', 'file' => 'SYS/xml.class'),

			// sessions
			'session' => array('class' => 'sys_session', 'file' => 'SYS/session.class'),
			'pagination' => array('class' => 'sys_pagination', 'file' => 'SYS/pagination.class'),

			// forms
			'form' => array('class' => 'sys_form', 'file' => 'SYS/form.class'),
			'form.fields' => array('class' => 'sys_field', 'file' => 'SYS/form.fields.class'),

			// constants
			'ppage' => array('list' => 25, 'form' => 9), // 'list': rows per page in lists, 'form': rows per page in <select>
		));
	}

	// this one will return the ini values from a ini file, to use as parm for the sys->ini_set() call
	function ini_load($ini_file)
	{
		$sys = &$GLOBALS[SYS];
		if ( $ini_file && ($ini_file = $sys->root . $ini_file . $sys->ext) && ($filename = sys::realpath($ini_file)) && file_exists($filename) )
		{
			$ini = false;
			include($ini_file);
			if ( $ini )
			{
				$sys->ini_set($ini);
			}
		}
	}

	function start_db()
	{
		$sys = &$GLOBALS[SYS];

		// db config filename
		if ( !($filename = $sys->ini_get('dbconfig', 'file')) )
		{
			return false;
		}

		// read the config file
		$dbms = $dbhost = $dbname = $dbuser = $dbpasswd = $dbpersistency = $dbprefix = $table_prefix = false;
		include($filename);
		$dbprefix = $table_prefix === false ? $dbprefix : $table_prefix;
		unset($table_prefix);
		if ( ($dbms === false) || empty($dbms) || ($dbhost === false) || ($dbuser === false) || ($dbpasswd === false) || ($dbprefix === false) )
		{
			return false;
		}

		// we may have already the db object
		if ( ($this->local_db = !isset($this->db) || ($this->db === false)) )
		{
			// get the available db layers
			if ( !($class = $sys->ini_get('db', 'class')) )
			{
				trigger_error('err_sys_db_layer_not_found', E_USER_ERROR);
			}
			if ( !($class = call_user_func(array($class, 'get_layer'), $dbms)) )
			{
				trigger_error('err_sys_db_not_supported', E_USER_ERROR);
			}
			$this->db = new $class($this->api_name);
			$this->db->set();
		}

		// try to connect the database
//--> reminder: thinking: we may need a connection object, in case we use an existing db with a different table prefix?
		if ( isset($this->db) && is_object($this->db) && !$this->db->open($dbprefix, $dbhost, $dbuser, $dbpasswd, $dbname, $dbpersistency) )
		{
			if ( $this->local_db )
			{
				sys::kill($this->db);
			}
			// nb.: {unset($this->db); + $this->db = false;} won't destroy $existing_object if we used {$this->db = &$existing_object;}
			// It only destroy the alias. This is a bit strange behaviour, but a very convenient one for our purpose :).
			unset($this->db);
			$this->db = false;
			trigger_error('err_sys_db_not_connected', E_USER_ERROR);
		}
		return true;
	}

	// build an url with sid
	function url($script, $parms=false, $no_ampersand=false)
	{
		$sys = &$GLOBALS[SYS];
		if ( is_array($script) )
		{
			$no_ampersand = $parms;
			$parms = $script;
			$script = '';
		}
		if ( empty($script) )
		{
			$script = $sys->requester;
		}
		if ( !$parms )
		{
			$parms = array();
		}
		if ( isset($this->session) && is_object($this->session) && $this->session->id && ($this->session->channel !== '_COOKIE') )
		{
			$parms += array($this->session->sid_name => $this->session->id);
		}
		if ( $parms && ($parms = $this->remove_dft_values($parms)) && ($parms = array_values(array_map(create_function('$key, $value', 'return empty($value) ? \'\' : $key . \'=\' . $value;'), array_keys($parms), array_values($parms)))) )
		{
			foreach ( $parms as $idx => $parm )
			{
				if ( empty($parm) )
				{
					unset($parms[$idx]);
				}
			}
		}
		return preg_replace('#^(\.\/)+#', '', $sys->root) . $script . $sys->ext . ($parms ? '?' . implode($no_ampersand ? '&' : '&amp;', $parms) : '');
	}
}

// this is the usual class to inherit, with the page handling
class sys_page extends sys_api
{
	var $rsc;

	function __construct($api_name)
	{
		parent::__construct($api_name);
		$this->rsc = array();
	}

	function __destruct()
	{
		unset($this->rsc);
		parent::__destruct();
	}

	// add uri ressources to page
	// - entry:
	//		o $name: ressource name: will be used as tpl switch by page_header
	//		o $data: flat value or single structure, or array of one of the both
	// nb.: in case $data is a structure, it must be a ready to send to tpl one, eg array('VARNAME1' => $value1, 'VARNAME2' => $value2, ...)
	function set_resource($name, $data)
	{
		if ( empty($name) || empty($data) )
		{
			return false;
		}
		if ( !isset($this->rsc[$name]) )
		{
			$this->rsc[$name] = array();
		}

		// we receive an array
		if ( is_array($data) )
		{
			// we receive a list of flat values
			$key = key($data);
			if ( $key === 0 )
			{
				foreach ( $data as $value )
				{
					$this->set_resource($name, $value);
				}
				return true;
			}

			// we receive a structure
			// php < 4.2.0 does not support in_array(array(), array()), so let's check doing a walk
			if ( !empty($this->rsc[$name]) )
			{
				foreach ( $this->rsc[$name] as $value )
				{
					$found = true;
					foreach ( $data as $key => $val )
					{
						if ( !($found = isset($value[$key]) && ($value[$key] == $val)) )
						{
							break;
						}
					}
					if ( $found )
					{
						return false;
					}
				}
			}
			$this->rsc[$name][] = $data;
			return true;
		}

		// we receive a flat value
		if ( empty($this->rsc[$name]) || !in_array($data, $this->rsc[$name]) )
		{
			$this->rsc[$name][] = $data;
			return true;
		}
		return false;
	}

	// display methods
	function page_header()
	{
		$sys = &$GLOBALS[SYS];
		$tpl = &$sys->tpl;
		$error = &$sys->error;

		// headers and script url
		$tpl->send_headers();
		$tpl->add(array(
			'U_SCRIPT' => $this->url($sys->requester),
		));

		// ressources (meta, jscripts, ...)
		if ( $this->rsc )
		{
			$rsc_names = array_keys($this->rsc);
			foreach ( $rsc_names as $rsc_name )
			{
				foreach ( $this->rsc[$rsc_name] as $rsc_data )
				{
					$tpl->add($rsc_name, is_array($rsc_data) ? $rsc_data : array('VALUE' => $rsc_data));
				}
			}
		}

		// warnings
		$error->send_warnings();
	}

	function page_footer()
	{
	}

	function display($tpl_name)
	{
		$sys = &$GLOBALS[SYS];
		$tpl = &$sys->tpl;

		$this->page_header();
		$tpl->parse($tpl_name);
		$this->page_footer();
		return true;
	}

	// send the message and redirect (delay is the delay prior redirecting):
	// feedback($msg, array(txt1 => url1, txt2 => url2), ..., delay)
	function feedback($msg, $links_back=false, $delay=false, $tpl_file=false)
	{
		$sys = &$GLOBALS[SYS];
		$tpl = &$sys->tpl;

		// check links back
		if ( !$links_back )
		{
			$links_back = array();
		}
		if ( !$tpl_file )
		{
			$tpl_file = 'feedback';
		}

		// add the "back to index" link
		$links_back['backto_index'] = $this->url($sys->requester);

		// default redirect delay
		if ( $delay === false )
		{
			$delay = 5;
		}

		// send message to tpl
		$tpl->add(array(
			'SCRIPT_TITLE' => 'sys_information',
			'MESSAGE' => $msg,
		));
		foreach ( $links_back as $title => $url )
		{
			$tpl->add('links', array(
				'L_BACK' => $title,
				'U_BACK' => $url,
			));
		}
		// if we are debugging, don't redirect automaticaly, except if delay=0
		if ( !isset($sys->rstats) || !is_object($sys->rstats) || !$sys->rstats->debug || ($delay === 0) )
		{
			@reset($links_back);
			$this->set_resource('meta', array(
				'HTTP_EQUIV' => 'refresh',
				'CONTENT' => $delay . '; url=' . current($links_back),
			));
			header('Refresh: ' . $delay . '; url=' . str_replace('&amp;', '&', current($links_back)));
		}
		$this->display($tpl_file);
		exit;
	}
}

?>